package com.example.demo.order.security;

import cn.hutool.core.util.IdUtil;
import cn.hutool.crypto.SecureUtil;
import com.example.demo.order.exception.BusinessException;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import jakarta.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName PaymentSecurityUtil
 * @Description 支付安全工具类
 * @Author MingChang.Wei
 * @Date 2025/9/26
 **/
@Slf4j
@Component
public class PaymentSecurityUtil {
    
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    
    @Resource
    private RateLimitUtil rateLimitUtil;
    
    private static final String PAYMENT_TOKEN_PREFIX = "payment_token:";
    private static final String PAYMENT_LOCK_PREFIX = "payment_lock:";
    private static final int TOKEN_EXPIRE_MINUTES = 30; // 支付令牌30分钟过期
    
    /**
     * 生成支付令牌 - 防止重复支付
     * @param orderNo 订单号
     * @param userId 用户ID
     * @param amount 支付金额
     * @return 支付令牌
     */
    public String generatePaymentToken(String orderNo, Long userId, BigDecimal amount) {
        try {
            // 生成唯一支付令牌
            String tokenId = IdUtil.fastSimpleUUID();
            String tokenKey = PAYMENT_TOKEN_PREFIX + orderNo;
            
            // 构建令牌数据
            PaymentTokenInfo tokenInfo = new PaymentTokenInfo();
            tokenInfo.setTokenId(tokenId);
            tokenInfo.setOrderNo(orderNo);
            tokenInfo.setUserId(userId);
            tokenInfo.setAmount(amount);
            tokenInfo.setCreateTime(LocalDateTime.now());
            tokenInfo.setUsed(false);
            
            // 存储到Redis，30分钟过期
            redisTemplate.opsForValue().set(tokenKey, tokenInfo, TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
            
            log.info("生成支付令牌: orderNo={}, userId={}, tokenId={}", orderNo, userId, tokenId);
            return tokenId;
            
        } catch (Exception e) {
            log.error("生成支付令牌失败: orderNo={}, userId={}", orderNo, userId, e);
            throw new BusinessException("生成支付令牌失败");
        }
    }
    
    /**
     * 验证并消费支付令牌
     * @param orderNo 订单号
     * @param tokenId 令牌ID
     * @param userId 用户ID
     * @param amount 支付金额
     */
    public void validateAndConsumePaymentToken(String orderNo, String tokenId, Long userId, BigDecimal amount) {
        try {
            String tokenKey = PAYMENT_TOKEN_PREFIX + orderNo;
            PaymentTokenInfo tokenInfo = (PaymentTokenInfo) redisTemplate.opsForValue().get(tokenKey);
            
            if (tokenInfo == null) {
                log.warn("支付令牌不存在或已过期: orderNo={}, tokenId={}", orderNo, tokenId);
                throw new BusinessException("支付令牌无效，请重新发起支付");
            }
            
            if (tokenInfo.isUsed()) {
                log.warn("支付令牌已使用: orderNo={}, tokenId={}", orderNo, tokenId);
                throw new BusinessException("支付令牌已使用，请勿重复支付");
            }
            
            if (!tokenInfo.getTokenId().equals(tokenId)) {
                log.warn("支付令牌不匹配: orderNo={}, expected={}, actual={}", orderNo, tokenInfo.getTokenId(), tokenId);
                throw new BusinessException("支付令牌验证失败");
            }
            
            if (!tokenInfo.getUserId().equals(userId)) {
                log.warn("支付令牌用户不匹配: orderNo={}, tokenUserId={}, currentUserId={}", orderNo, tokenInfo.getUserId(), userId);
                throw new BusinessException("支付令牌用户验证失败");
            }
            
            if (tokenInfo.getAmount().compareTo(amount) != 0) {
                log.warn("支付令牌金额不匹配: orderNo={}, tokenAmount={}, payAmount={}", orderNo, tokenInfo.getAmount(), amount);
                throw new BusinessException("支付金额验证失败");
            }
            
            // 标记令牌为已使用
            tokenInfo.setUsed(true);
            tokenInfo.setUseTime(LocalDateTime.now());
            redisTemplate.opsForValue().set(tokenKey, tokenInfo, TOKEN_EXPIRE_MINUTES, TimeUnit.MINUTES);
            
            log.info("支付令牌验证成功并已消费: orderNo={}, tokenId={}", orderNo, tokenId);
            
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            log.error("验证支付令牌失败: orderNo={}, tokenId={}", orderNo, tokenId, e);
            throw new BusinessException("支付令牌验证异常");
        }
    }
    
    /**
     * 生成支付签名 - 防止参数篡改
     * @param orderNo 订单号
     * @param userId 用户ID
     * @param amount 金额
     * @param timestamp 时间戳
     * @param secretKey 密钥
     * @return 签名
     */
    public String generatePaymentSignature(String orderNo, Long userId, BigDecimal amount, long timestamp, String secretKey) {
        String data = String.format("orderNo=%s&userId=%d&amount=%s&timestamp=%d&key=%s", 
            orderNo, userId, amount.toString(), timestamp, secretKey);
        return SecureUtil.md5(data).toUpperCase();
    }
    
    /**
     * 验证支付签名
     * @param orderNo 订单号
     * @param userId 用户ID
     * @param amount 金额
     * @param timestamp 时间戳
     * @param signature 签名
     * @param secretKey 密钥
     */
    public void validatePaymentSignature(String orderNo, Long userId, BigDecimal amount, long timestamp, String signature, String secretKey) {
        // 检查时间戳有效性（5分钟内有效）
        long currentTime = System.currentTimeMillis();
        if (Math.abs(currentTime - timestamp) > 5 * 60 * 1000) {
            log.warn("支付请求时间戳过期: orderNo={}, timestamp={}, current={}", orderNo, timestamp, currentTime);
            throw new BusinessException("支付请求已过期，请重新发起");
        }
        
        // 生成期望签名
        String expectedSignature = generatePaymentSignature(orderNo, userId, amount, timestamp, secretKey);
        
        // 验证签名
        if (!expectedSignature.equals(signature)) {
            log.warn("支付签名验证失败: orderNo={}, expected={}, actual={}", orderNo, expectedSignature, signature);
            throw new BusinessException("支付参数签名验证失败");
        }
        
        log.debug("支付签名验证成功: orderNo={}", orderNo);
    }
    
    /**
     * 检查支付限流
     * @param userId 用户ID
     */
    public void checkPaymentRateLimit(Long userId) {
        rateLimitUtil.checkPaymentLimit(userId);
    }
    
    /**
     * 获取支付锁key
     * @param orderNo 订单号
     * @return 锁key
     */
    public String getPaymentLockKey(String orderNo) {
        return PAYMENT_LOCK_PREFIX + orderNo;
    }
    
    /**
     * 记录支付安全事件
     * @param eventType 事件类型
     * @param orderNo 订单号
     * @param userId 用户ID
     * @param details 详情
     */
    public void logPaymentSecurityEvent(String eventType, String orderNo, Long userId, String details) {
        log.warn("支付安全事件 - 类型: {}, 订单号 {}, 用户ID: {}, 详情: {}, 时间: {}", 
            eventType, orderNo, userId, details, System.currentTimeMillis());
        
        // 集成到安全监控系统
        try {
            // 可以发送告警、记录到安全日志数据库等
            // 例如：alertService.sendSecurityAlert(eventType, orderNo, userId, details);
            // 或者：securityEventRepository.save(securityEvent);
        } catch (Exception e) {
            log.error("记录支付安全事件失败", e);
        }
    }
    
    /**
     * 支付令牌信息类
     */
    @Data
    public static class PaymentTokenInfo {
        private String tokenId;
        private String orderNo;
        private Long userId;
        private BigDecimal amount;
        private LocalDateTime createTime;
        private LocalDateTime useTime;
        private boolean used;
    }
}
